// Copyright (c) 2023 Presto Labs Pte. Ltd.
// Author: tianqiang

#include "coin2/exchange/bit/order_v1/parser.h"

namespace coin2::exchange::bit::order_v1 {

bool BitParser::IsBitSpotErrMessage(const JsonValue& doc) {
  auto error_code = GetErrorCode(doc);
  auto error_msg = GetErrorMsg(doc);

  return !error_code || !error_msg || error_code.value() != 0 || !error_msg.value().empty();
}

int64_t BitParser::GetProcOrderId(const JsonValue& data) {
  if (!data.HasMember("label") || !data["label"].IsString() || data["label"].IsNull()) {
    return 0;
  }

  return stoll(data["label"].GetString());
}

coin::proto::OrderSide BitParser::GetOrderSide(const std::string& side) {
  if (side == "buy") {
    return coin::proto::OrderSide::BUY_ORDER;
  } else if (side == "sell") {
    return coin::proto::OrderSide::SELL_ORDER;
  } else {
    return coin::proto::OrderSide::UNKNOWN_ORDER_SIDE;
  }
}

coin::proto::OrderType BitParser::GetOrderType(const std::string& type) {
  if (type == "limit") {
    return coin::proto::OrderType::LIMIT_ORDER;
  } else if (type == "market") {
    return coin::proto::OrderType::MARKET_ORDER;
  } else {
    return coin::proto::OrderType::UNKNOWN_ORDER_TYPE;
  }
}

std::string BitParser::GetFeeCurrency(const JsonValue& data) {
  std::string fee_currency = "UNKNOW";

  if (!data.HasMember("pair") || !data.HasMember("side")) {
    LOG(ERROR) << "[GetFeeCurrency] Invalid data format\n" << JsonToString(data);
    return fee_currency;
  }

  std::string pair(data["pair"].GetString());

  std::vector<std::string> splited = Split(pair, "-");

  if (splited.size() != 2) {
    LOG(ERROR) << "[GetFeeCurrency] Invalid pair:" << pair;
    return fee_currency;
  }

  auto side = GetOrderSide(data["side"].GetString());
  switch (side) {
    case coin::proto::OrderSide::BUY_ORDER:
      fee_currency = splited[0];
      break;
    case coin::proto::OrderSide::SELL_ORDER:
      fee_currency = splited[1];
      break;
    default:
      LOG(ERROR) << "[GetFeeCurrency] Invalid side:" << data["side"].GetString();
      break;
  }

  return fee_currency;
}

void BitParser::SetOrderStatus(ProductOrderElement* order, const std::string& status) {
  if (status == "open") {
    order->set_is_live(true);
  } else if (status == "cancelled") {
    order->set_is_live(false);
    order->set_cancel_confirmed(true);
  } else if (status == "filled") {
    order->set_is_live(false);
    order->set_fully_filled(true);
  } else if (status == "pending") {
    // ignore pending status
  } else {
    LOG(ERROR) << "[SetOrderStatus] Unknown order status: " << status;
  }
}

void BitParser::ParseAccountBalance() {
  JsonDocument doc;
  if (!PrepareJsonDocument(&doc)) {
    LOG(ERROR) << "[ParseAccountBalance] Parsing error\n" << msg_->DebugString();
    result_.set_error_code(OrderErrorCode::INVALID_JSON);
    return;
  }

  auto account_balance = ParseBalance(doc);
  if (!account_balance.has_exchange() || !account_balance.has_market_type()) {
    result_.set_error_code(OrderErrorCode::INVALID_JSON);
    return;
  }
  result_.set_type(MsgType::ACCOUNT_BALANCE);
  *result_.mutable_account_balance() = std::move(account_balance);
}

// NOTE: when field exchange and market_type are set, which means parsed successfully
AccountBalance BitParser::ParseBalance(const JsonValue& doc) {
  RawBalanceMap raw_balance_map;
  auto success = ParseRawBalance(doc, &raw_balance_map);
  if (!success) {
    return {};
  }
  AccountBalance acc_balance;
  acc_balance.set_exchange("Bit");
  acc_balance.set_market_type("Spot");
  for (const auto& native_currency : symcache_->GetCurrenciesNativeUpper()) {
    auto* balance = acc_balance.add_each_balance();
    if (raw_balance_map.count(native_currency) == 0) {
      balance->set_total(0);
      balance->set_hold(0);
      balance->set_available(0);
    } else {
      balance->MergeFrom(raw_balance_map.at(native_currency));
    }
    auto* currency = symcache_->GetCurrencyByNative(native_currency);
    balance->set_currency(currency->currency());
    balance->set_currency_native(currency->native_currency());
    DLOG(INFO) << "BALANCE: " << currency->native_currency() << " " << balance->total() << " "
               << balance->available();
  }
  is_account_balance_initialized_ = true;

  return acc_balance;
}

bool BitParser::ParseRawBalance(const JsonValue& doc, RawBalanceMap* raw_balance_map) {
  /*
  {
    "code":0,
    "message":"",
    "data":{
      "user_id":2930832,
      "created_at":1678087138825,
      "total_collateral":"492.04149212",
      "total_margin_balance":"492.04149212",
      "total_available":"489.90556069",
      "total_initial_margin":"0.00000000",
      "total_maintenance_margin":"0.00000000",
      "total_initial_margin_ratio":"0.00434096",
      "total_maintenance_margin_ratio":"0.00000000",
      "total_liability":"0.00000000",
      "total_unsettled_amount":"0.00000000",
      "total_future_value":"0.00000000",
      "total_option_value":"0.00000000",
      "spot_orders_hc_loss":"2.13593143",
      "total_position_pnl":"0.00000000",
      "details":[
        {
          "currency":"USDT",
          "ccy_display_name":"USDT",
          "equity":"422.64906075",
          "liability":"0.00000000",
          "index_price":"1.00005858",
          "cash_balance":"422.64906075",
          "margin_balance":"422.64906075",
          "available_balance":"408.11306075",
          "initial_margin":"0.00000000",
          "spot_margin":"14.53600000",
          "maintenance_margin":"0.00000000",
          "potential_liability":"0.00000000",
          "interest":"0.00000000",
          "interest_rate":"0.06750000",
          "pnl":"0.00000000",
          "total_delta":"0.00000000",
          "session_rpl":"0.00000000",
          "session_upl":"0.00000000",
          "option_value":"0.00000000",
          "option_pnl":"0.00000000",
          "option_session_rpl":"0.00000000",
          "option_session_upl":"0.00000000",
          "option_delta":"0.00000000",
          "option_gamma":"0.00000000",
          "option_vega":"0.00000000",
          "option_theta":"0.00000000",
          "future_value":"0.00000000",
          "future_pnl":"0.00000000",
          "future_session_rpl":"0.00000000",
          "future_session_upl":"0.00000000",
          "future_session_funding":"0.00000000",
          "future_delta":"0.00000000",
          "future_available_balance":"408.11306075",
          "option_available_balance":"408.11306075",
          "unsettled_amount":"0.00000000",
          "liq_price":"0",
          "usdt_index_price":"1.00005858"
        }
      ]
    }
  }
  */
  if (IsBitSpotErrMessage(doc)) {
    LOG(ERROR) << "[ParseBalance] Error message\n" << JsonToString(doc);
    return false;
  }

  if (!doc.HasMember("data") || !doc["data"].HasMember("details") ||
      !doc["data"]["details"].IsArray()) {
    LOG(ERROR) << "[ParseBalance] Invalid data format\n" << JsonToString(doc);
    return false;
  }

  for (const auto& data : doc["data"]["details"].GetArray()) {
    if (!data.HasMember("currency") || !data.HasMember("available_balance") ||
        !data.HasMember("spot_margin")) {
      continue;
    }
    CurrencyBalance currency_balance;
    currency_balance.set_currency_native(data["currency"].GetString());
    auto avail = stod(data["available_balance"].GetString());
    auto hold = stod(data["spot_margin"].GetString());
    currency_balance.set_available(avail);
    currency_balance.set_total(avail + hold);
    currency_balance.set_hold(hold);
    raw_balance_map->emplace(currency_balance.currency_native(), currency_balance);
  }

  return true;
}

void BitParser::ParseOpenOrderInfo() { ParseOrderInfos(__func__); }

void BitParser::ParseHistoryOrderInfo() { ParseOrderInfos(__func__); }

void BitParser::ParseOrderInfos(std::string func_name) {
  /*
  {
    "code":0,
    "message":"",
    "data":[
      {
        "order_id":"621745471",
        "created_at":1678084544787,
        "updated_at":1678084544787,
        "user_id":"2930832",
        "pair":"XRP-USDT",
        "order_type":"limit",
        "side":"buy",
        "price":"0.36340000",
        "qty":"40.00000000",
        "quote_qty":"0.00000000",
        "time_in_force":"gtc",
        "avg_price":"0.00000000",
        "filled_qty":"0.00000000",
        "status":"open",
        "fee":"0.00000000",
        "taker_fee_rate":"0.00100000",
        "maker_fee_rate":"0.00080000",
        "cancel_reason":"",
        "label":"1678082495963629885",
        "source":"api",
        "post_only":true,
        "reject_post_only":true,
        "mmp":false,
        "is_liquidation":false,
        "is_um":true,
        "reorder_index":42092871987,
        "display_name":"XRP-USDT"
      }
    ]
  }
  */
  JsonDocument doc;
  if (!PrepareJsonDocument(&doc)) {
    LOG(ERROR) << "[" + func_name + "] Parsing error\n" << msg_->DebugString();
    result_.set_error_code(OrderErrorCode::INVALID_JSON);
    return;
  }

  if (IsBitSpotErrMessage(doc)) {
    LOG(ERROR) << "[" + func_name + "] Error message\n" << JsonToString(doc);
    return;
  }

  if (!doc.HasMember("data") || !doc["data"].IsArray()) {
    LOG(ERROR) << "[" + func_name + "] Invalid data format\n" << JsonToString(doc);
    return;
  }

  auto product_order_bundle = result_.mutable_order_list();
  product_order_bundle->Clear();
  bool product_order_bundle_found = false;
  for (const auto& data : doc["data"].GetArray()) {
    if (!data.HasMember("pair")) {
      continue;
    }
    std::string native_symbol = data["pair"].GetString();
    if (!symcache_->has_product_native(native_symbol)) {
      continue;
    }
    auto order = product_order_bundle->add_each_order();
    ParseOrderInfo(order, data);
    product_order_bundle_found = true;
  }

  if (product_order_bundle_found) {
    result_.set_type(MsgType::ORDER_STATUS);
  }
}

void BitParser::ParseOrderInfo(ProductOrderElement* order, const JsonValue& data) {
  order->set_exchange_type(ExchangeType::Bit);
  order->set_market_type(MarketType::Spot);
  order->set_api_version("v1");
  order->set_exchange_order_id(data["order_id"].GetString());
  order->set_symbol(data["pair"].GetString());
  order->set_price(stod(data["price"].GetString()));
  order->set_qty(std::fabs(stod(data["qty"].GetString())));
  order->set_proc_order_id(GetProcOrderId(data));
  order->set_side(GetOrderSide(data["side"].GetString()));
  order->set_order_type(GetOrderType(data["order_type"].GetString()));
  order->set_timestamp(GetCurrentTimestamp());
  SetOrderStatus(order, data["status"].GetString());
}

void BitParser::ParseFillOrderInfo() {
  /*
  {
    "code":0,
    "message":"",
    "data":[
      {
        "trade_id":"78465433",
        "order_id":"621745590",
        "pair":"ETH-USDT",
        "qty":"0.00800000",
        "price":"1563.45000000",
        "fee":"0.01000608",
        "fee_rate":"0.00080000",
        "side":"sell",
        "created_at":1678086676844,
        "is_taker":false,
        "order_type":"limit",
        "label":"1678082495963629890",
        "display_name":"ETH-USDT"
      }
    ]
  }
  */
  JsonDocument doc;
  if (!PrepareJsonDocument(&doc)) {
    LOG(ERROR) << "[ParseFillOrderInfo] Parsing error\n" << msg_->DebugString();
    result_.set_error_code(OrderErrorCode::INVALID_JSON);
    return;
  }

  if (IsBitSpotErrMessage(doc)) {
    LOG(ERROR) << "[ParseFillOrderInfo] Error message\n" << JsonToString(doc);
    return;
  }

  if (!doc.HasMember("data") || !doc["data"].IsArray()) {
    LOG(ERROR) << "[ParseFillOrderInfo] Invalid data format\n" << JsonToString(doc);
    return;
  }

  auto product_fill_bundle = result_.mutable_fill_list();
  product_fill_bundle->Clear();
  bool product_fill_bundle_found = false;
  for (const auto& data : doc["data"].GetArray()) {
    auto fill = product_fill_bundle->add_each_fill();
    fill->Clear();
    ParseFillInfo(fill, data);
    product_fill_bundle_found = true;
  }

  if (product_fill_bundle_found) {
    result_.set_type(MsgType::ORDER_FILL);
  }
}

void BitParser::ParseFillInfo(ProductFillElement* fill, const JsonValue& data) {
  fill->set_symbol(data["pair"].GetString());
  fill->set_exchange_order_id(data["order_id"].GetString());
  fill->set_proc_order_id(GetProcOrderId(data));
  fill->set_price(stod(data["price"].GetString()));
  fill->set_qty(std::fabs(stod(data["qty"].GetString())));
  fill->set_fill_id(data["trade_id"].GetString());
  fill->set_side(GetOrderSide(data["side"].GetString()));
  fill->set_fee(stod(data["fee"].GetString()));
  fill->set_fee_currency(GetFeeCurrency(data));
  fill->set_fill_type(
      data["is_taker"].GetBool() ? coin::proto::FillType::TAKER_FILL_TYPE
                                 : coin::proto::FillType::MAKER_FILL_TYPE);
  fill->set_fill_timestamp(GetCurrentTimestamp());
}

void BitParser::ParseListenKey() {
  /*
  {
    "code":0,
    "message":"",
    "data":{
      "token":"m5VsMXoa_Ke7tyQQXclKuBimmW7pdrDMywZ7IN1XDzN0HTwGrE_VN28QuwrHWwh7wFSHLC5d62ESu5cvdLydcA50b51f8e600c90d0a7d88418d947f6f7"
    }
  }
  */
  JsonDocument doc;
  if (!PrepareJsonDocument(&doc)) {
    LOG(ERROR) << "[ParseListenKey] Parsing error\n" << msg_->DebugString();
    result_.set_error_code(OrderErrorCode::INVALID_JSON);
    return;
  }

  if (IsBitSpotErrMessage(doc)) {
    LOG(ERROR) << "[ParseListenKey] Error message\n" << JsonToString(doc);
    return;
  }

  if (!doc.HasMember("data") || !doc["data"].HasMember("token") ||
      !doc["data"]["token"].IsString()) {
    LOG(ERROR) << "[ParseListenKey] Invalid data format\n" << JsonToString(doc);
    return;
  }

  std::string token(doc["data"]["token"].GetString());
  mb_->PublishEvent("bit/listen_key", &token);
}

void BitParser::ParseSubmitResponse() {
  /*
  {
    "code":0,
    "message":"",
    "data":{
      "order_id":"621791686",
      "created_at":1678087153866,
      "updated_at":1678087153866,
      "user_id":"2930832",
      "pair":"BTC-USDT",
      "order_type":"limit",
      "side":"sell",
      "price":"22423.78000000",
      "qty":"0.00050000",
      "quote_qty":"0.00000000",
      "time_in_force":"gtc",
      "avg_price":"0.00000000",
      "filled_qty":"0.00000000",
      "status":"open",
      "fee":"0.00000000",
      "taker_fee_rate":"0.00100000",
      "maker_fee_rate":"0.00080000",
      "cancel_reason":"",
      "label":"1678087138253189409",
      "source":"api",
      "post_only":true,
      "reject_post_only":true,
      "mmp":false,
      "is_liquidation":false,
      "is_um":true,
      "reorder_index":0,
      "display_name":"BTC-USDT"
    }
  }
  */
  const auto order_context = msg_->oc;
  const auto& http_context = msg_->http_context;
  if (!IsValidResponse(http_context)) {
    return;
  }

  result_.set_type(MsgType::SUBMIT_RESPONSE);
  auto resp = result_.mutable_submit_response();
  resp->Clear();
  resp->set_symbol(order_context->order_spec().product);
  resp->set_proc_order_id(order_context->proc_order_id());
  resp->set_http_status(http_context->res.result_int());
  resp->set_timestamp(GetCurrentTimestamp());
  resp->set_success(false);

  if (http_context->status == AsioHttpContextStatus::kError) {
    LOG(ERROR) << "[ParseSubmitResponse] AsioHttpContextStatus error";
    resp->set_error_code(coin::proto::OrderErrorCode::ORDER_NETWORK_ERROR);
    return;
  }

  JsonDocument json;
  if (!PrepareJsonDocument(&json)) {
    LOG(ERROR) << "[ParseSubmitResponse] Parsing error\n" << http_context->res;
    resp->set_error_code(coin::proto::OrderErrorCode::ORDER_API_ERROR);
    return;
  }

  auto exchange_error_code = GetErrorCode(json);
  auto exchange_error_msg = GetErrorMsg(json);

  if (!exchange_error_code || !exchange_error_msg) {
    LOG(ERROR) << "[ParseSubmitResponse] Parsing error\n" << http_context->res;
    resp->set_error_code(coin::proto::OrderErrorCode::ORDER_API_ERROR);
    return;
  }

  auto error_code = GetOrderErrorCode(exchange_error_code.value());
  resp->set_error_code(error_code);
  resp->set_exchange_error_code(std::to_string(exchange_error_code.value()));
  resp->set_exchange_error_msg(exchange_error_msg.value());
  if (error_code != coin::proto::OrderErrorCode::ORDER_NO_ERROR) {
    LOG(ERROR) << "[ParseSubmitResponse] Error response\n" << http_context->res;
    return;
  }

  if (!json.HasMember("data") || !json["data"].HasMember("order_id") ||
      !json["data"]["order_id"].IsString()) {
    LOG(ERROR) << "[ParseSubmitResponse] Invalid data format\n" << JsonToString(json);
    resp->set_error_code(coin::proto::OrderErrorCode::ORDER_API_ERROR);
    return;
  }

  resp->set_exchange_order_id(json["data"]["order_id"].GetString());
  resp->set_success(true);
}

void BitParser::ParseCancelResponse() {
  /*
  {
    "code":0,
    "message":"",
    "data":{
      "num_cancelled":1,
      "order_ids":[
        621791624
      ]
    }
  }
  */
  const auto order_context = msg_->oc;
  const auto& http_context = msg_->http_context;
  if (!IsValidResponse(http_context)) {
    return;
  }

  result_.set_type(MsgType::CANCEL_RESPONSE);
  auto resp = result_.mutable_cancel_response();
  resp->Clear();
  resp->set_symbol(order_context->order_spec().product);
  resp->set_proc_order_id(order_context->proc_order_id());
  resp->set_http_status(http_context->res.result_int());
  resp->set_exchange_order_id(order_context->exchange_order_id());
  resp->set_timestamp(GetCurrentTimestamp());
  resp->set_success(false);

  if (http_context->status == AsioHttpContextStatus::kError) {
    LOG(ERROR) << "[ParseCancelResponse] AsioHttpContextStatus error";
    resp->set_error_code(coin::proto::OrderErrorCode::ORDER_NETWORK_ERROR);
    return;
  }

  JsonDocument json;
  if (!PrepareJsonDocument(&json)) {
    LOG(ERROR) << "[ParseCancelResponse] Parsing error\n" << http_context->res;
    resp->set_error_code(coin::proto::OrderErrorCode::ORDER_API_ERROR);
    return;
  }

  auto exchange_error_code = GetErrorCode(json);
  auto exchange_error_msg = GetErrorMsg(json);

  if (!exchange_error_code || !exchange_error_msg) {
    LOG(ERROR) << "[ParseCancelResponse] Parsing error\n" << http_context->res;
    resp->set_error_code(coin::proto::OrderErrorCode::ORDER_API_ERROR);
    return;
  }

  auto error_code = GetOrderErrorCode(exchange_error_code.value());
  resp->set_error_code(error_code);
  resp->set_exchange_error_code(std::to_string(exchange_error_code.value()));
  resp->set_exchange_error_msg(exchange_error_msg.value());
  if (error_code != coin::proto::OrderErrorCode::ORDER_NO_ERROR) {
    LOG(ERROR) << "[ParseCancelResponse] Error response\n" << http_context->res;
    return;
  }

  if (!json.HasMember("data") || !json["data"].HasMember("order_ids") ||
      !json["data"]["order_ids"].IsArray() || json["data"]["order_ids"].GetArray().Size() > 1) {
    LOG(ERROR) << "[ParseCancelResponse] Invalid data format\n" << JsonToString(json);
    resp->set_error_code(coin::proto::OrderErrorCode::ORDER_API_ERROR);
    return;
  }

  if (json["data"]["order_ids"].GetArray().Size() == 0) {
    resp->set_error_code(coin::proto::OrderErrorCode::ORDER_NOT_FOUND);
    resp->set_exchange_error_code(std::to_string(18100115));
    resp->set_exchange_error_msg("Order Not Found");

    LOG(ERROR) << "[ParseCancelResponse] Order not found\n" << http_context->res;
    return;
  }

  resp->set_exchange_order_id(json["data"]["order_ids"][0].GetString());
  resp->set_success(true);
  resp->set_cancel_confirmed(true);
}

int32_t BitParser::ParseErrorMessage(const std::shared_ptr<AsioHttpContext>& http_context) {
  const auto http_code = http_context->res.result_int();
  if (http_code == 401) {
    LOG(ERROR) << "[ParseErrorMessage] Unauthorized-Authentication failed\n" << msg_->DebugString();
  } else if (http_code == 403) {
    LOG(ERROR) << "[ParseErrorMessage] Access denied\n" << msg_->DebugString();
  } else if (http_code == 404) {
    LOG(ERROR) << "[ParseErrorMessage] Request path not found\n" << msg_->DebugString();
  } else if (http_code == 429) {
    // auto enable_time = 5 * 1'000'000'000LL + GetCurrentTimestamp();
    // mb_->PublishEvent("bit/enable_time", reinterpret_cast<void*>(enable_time));
    LOG(ERROR) << "[ParseErrorMessage] Rate Limit Exceeded\n" << msg_->DebugString();
  } else if (http_code == 500) {
    LOG(WARNING) << "[ParseErrorMessage] Bit service problem\n" << msg_->DebugString();
  }

  return http_code;
}

void BitParser::ParseWsOrderInfoList(const JsonDocument& document) {
  /*
  {
  {
    "channel":"order",
    "timestamp":1678087156871,
    "data":[
      {
        "order_id":"621791772",
        "created_at":1678087156858,
        "updated_at":1678087156858,
        "user_id":"2930832",
        "pair":"BTC-USDT",
        "order_type":"limit",
        "side":"sell",
        "price":"22424.06000000",
        "qty":"0.00050000",
        "quote_qty":"0.00000000",
        "time_in_force":"gtc",
        "avg_price":"0.00000000",
        "filled_qty":"0.00000000",
        "status":"pending",
        "fee":"0.00000000",
        "taker_fee_rate":"0.00100000",
        "maker_fee_rate":"0.00080000",
        "cancel_reason":"",
        "label":"1678087138253189410",
        "source":"api",
        "post_only":true,
        "reject_post_only":true,
        "mmp":false,
        "is_liquidation":false,
        "is_um":false,
        "reorder_index":42098607315,
        "display_name":"BTC-USDT"
      }
    ],
    "module":"spot"
  }
  */
  if (!document.HasMember("data") || !document["data"].IsArray()) {
    LOG(ERROR) << "[ParseWsOrderInfoList] Invalid data format\n" << JsonToString(document);
    return;
  }

  auto product_order_bundle = result_.mutable_order_list();
  product_order_bundle->Clear();

  bool product_order_bundle_found = false;
  for (const auto& data : document["data"].GetArray()) {
    auto order = product_order_bundle->add_each_order();

    ParseOrderInfo(order, data);

    product_order_bundle_found = true;
  }

  if (product_order_bundle_found) {
    result_.set_type(MsgType::ORDER_STATUS);
  }
}

void BitParser::ParseWsTradeInfoList(const JsonDocument& document) {
  /*
  {
    "channel":"user_trade",
    "timestamp":1678090943671,
    "data":[
      {
        "trade_id":"78482434",
        "order_id":"621850206",
        "pair":"XRP-USDT",
        "qty":"40.00000000",
        "price":"0.36010000",
        "fee":"0.03200000",
        "fee_rate":"0.00080000",
        "side":"buy",
        "created_at":1678090943637,
        "is_taker":false,
        "order_type":"limit",
        "label":"1678090041463345377",
        "display_name":"XRP-USDT"
      }
    ],
    "module":"spot"
  }
  */
  if (!document.HasMember("data") || !document["data"].IsArray()) {
    LOG(ERROR) << "[ParseWsTradeInfoList] Invalid data format\n" << JsonToString(document);
    return;
  }

  ProductFillBundle* product_fill_bundle = result_.mutable_fill_list();
  product_fill_bundle->Clear();

  bool product_fill_bundle_found = false;
  for (const auto& data : document["data"].GetArray()) {
    if (!data.HasMember("trade_id")) {
      continue;
    }

    auto fill = product_fill_bundle->add_each_fill();

    ParseFillInfo(fill, data);

    product_fill_bundle_found = true;
  }

  if (product_fill_bundle_found) {
    result_.set_type(MsgType::ORDER_FILL);
  }
}

void BitParser::ParseWsBalanceInfoList(const JsonDocument& document) {
  /*
  {
    "channel":"um_account",
    "timestamp":1632439007081,
    "module":"um",
    "data":{
      "user_id":1,
      "created_at":1632642549581,
      "usdt_total_collateral":"200.00000000",
      "usdt_total_margin_balance":"190.00000000",
      "usdt_total_available":"80.00000000",
      "usdt_total_initial_margin":"90.00000000",
      "usdt_total_maintenance_margin":"120.00000000",
      "usdt_total_initial_margin_ratio":"0.60000000",
      "usdt_total_maintenance_margin_ratio":"0.60000000",
      "usdt_total_liability":"8060000.00000000",
      "usdt_total_unsettled_amount":"322143.95604395",
      "spot_orders_hc_loss":"30.00000000",
      "details":[
        {
          "available_balance":"410.12307995",
          "cash_balance":"410.12307995",
          "ccy_display_name":"USDT",
          "currency":"USDT",
          "equity":"410.12307995",
          "future_available_balance":"410.12307995",
          "future_delta":"0.00000000",
          "future_pnl":"0.00000000",
          "future_session_funding":"0.00000000",
          "future_session_rpl":"0.00000000",
          "future_session_upl":"0.00000000",
          "future_value":"0.00000000",
          "index_price":"1.00005572",
          "initial_margin":"0.00000000",
          "interest":"0.00000000",
          "interest_rate":"0.06750000",
          "liability":"0.00000000",
          "liq_price":"0",
          "maintenance_margin":"0.00000000",
          "margin_balance":"410.12307995",
          "option_available_balance":"410.12307995",
          "option_delta":"0.00000000",
          "option_gamma":"0.00000000",
          "option_pnl":"0.00000000",
          "option_session_rpl":"0.00000000",
          "option_session_upl":"0.00000000",
          "option_theta":"0.00000000",
          "option_value":"0.00000000",
          "option_vega":"0.00000000",
          "pnl":"0.00000000",
          "potential_liability":"0.00000000",
          "session_rpl":"0.00000000",
          "session_upl":"0.00000000",
          "spot_margin":"0.00000000",
          "total_delta":"0.00000000",
          "unsettled_amount":"0.00000000",
          "usdt_index_price":"1.00005572"
        }
      ]
    }
  }
  */
  if (!is_account_balance_initialized_) {
    return;
  }

  if (!document.HasMember("data") || !document["data"].HasMember("details") ||
      !document["data"]["details"].IsArray()) {
    LOG(ERROR) << "[ParseWsBalanceInfoList] Invalid data format\n" << JsonToString(document);
    return;
  }

  auto acc_balance = result_.mutable_account_balance();
  acc_balance->Clear();
  acc_balance->set_exchange("Bit");
  acc_balance->set_market_type("Spot");

  bool account_balance_found = false;
  for (const auto& data : document["data"]["details"].GetArray()) {
    if (!data.HasMember("currency") || !data.HasMember("available_balance") ||
        !data.HasMember("spot_margin")) {
      continue;
    }
    if (!symcache_->has_currency_native(data["currency"].GetString())) {
      continue;
    }
    auto* currency = symcache_->GetCurrencyByNative(data["currency"].GetString());
    auto* balance = acc_balance->add_each_balance();
    balance->set_currency(currency->currency());
    balance->set_currency_native(currency->native_currency());
    auto avail = stod(data["available_balance"].GetString());
    auto hold = stod(data["spot_margin"].GetString());
    balance->set_available(avail);
    balance->set_total(avail + hold);
    balance->set_hold(hold);
    account_balance_found = true;
  }

  if (account_balance_found) {
    result_.set_type(MsgType::ACCOUNT_BALANCE);
  }
}

void BitParser::ParseWsMessage() {
  JsonDocument document;
  if (!PrepareJsonDocument(&document)) {
    LOG(ERROR) << "[ParseWsMessage] Parsing Error. " << msg_->DebugString();
    result_.set_error_code(OrderErrorCode::INVALID_JSON);
    return;
  }

  DLOG(INFO) << "OG ws msg: " << JsonToString(document);

  if (!document.HasMember("channel") || !document["channel"].IsString()) {
    LOG(ERROR) << "[ParseWsMessage] Invalid data format\n" << JsonToString(document);
    return;
  }

  std::string channel(document["channel"].GetString());
  if (channel == "order") {
    ParseWsOrderInfoList(document);
  } else if (channel == "user_trade") {
    ParseWsTradeInfoList(document);
  } else if (channel == "um_account") {
    ParseWsBalanceInfoList(document);
  }
}

auto BitParser::ParseRawMessage(const RawMessage& msg) -> const ParseResult& {
  result_.Clear();
  msg_ = &msg;
  sub_topic_id_ = static_cast<OrderSubTopicId>(msg_->type);

  if (msg.type == OrderSubTopicId::WS_MESSAGE) {
    ParseWsMessage();
    return result_;
  }

  CHECK(msg.type == OrderSubTopicId::REST_MESSAGE);

  ParseErrorMessage(msg.http_context);

  switch (msg.http_context->query_type) {
    case OrderSubTopicId::SUBMIT_RESPONSE:
      ParseSubmitResponse();
      break;
    case OrderSubTopicId::CANCEL_RESPONSE:
      ParseCancelResponse();
      break;
    case OrderSubTopicId::REST_ACCOUNT_BALANCE:
      ParseAccountBalance();
      break;
    case OrderSubTopicId::REST_OPEN_ORDERS:
      ParseOpenOrderInfo();
      break;
    case OrderSubTopicId::REST_DONE_ORDERS:
      ParseHistoryOrderInfo();
      break;
    case OrderSubTopicId::REST_FILL_ORDERS:
      ParseFillOrderInfo();
      break;
    case OrderSubTopicId::LISTEN_KEY:
      ParseListenKey();
      break;
    default: {
      LOG(ERROR) << "[ParseRawMessage] Unknown query type\n" << msg_->DebugString();
      result_.set_error_code(OrderErrorCode::ORDER_UNKNOWN_ERROR);
      break;
    }
  }

  return result_;
}

}  // namespace coin2::exchange::bit::order_v1
